package datatypes;

import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import java.util.Vector;

/**
 * the User of this server becomes automatically an listener, at the moment an Instance of the server is created
 * 
 * the Update Method in Observer 
 * has to cache the answer, 
 * because its posiblle that the update
 * is processed before an return value is given.
 * 
 * @author Alex
 *
 */
public class RegistryServer extends Observable {
	
	//QueueOrganisation
	public enum action_type {
		FIND_SPECIAL,FIND_ALL,PUBLISH,UPDATE,FIND_HOST_SERVICES, NOTIFY, PINGED
	}
	
	private static final int TIMEOUT = 10;
	private int personalTimeoutSequence = TIMEOUT;
	//how often every device is pinged
	private static final int NOTIFY_SEQUENCE = 5;
	
	private static int lastGlobalMsgID = Integer.MIN_VALUE;
	
	private boolean activated = false;

	
	//ProviderAddress --> Provider-BluetoothAdress
	HashMap<String, BluetoothDeviceAddress> providerList = new HashMap <String, BluetoothDeviceAddress>();
	//ProviderAddress --> Services
	HashMap<String, Vector<String>> serviceList = new HashMap <String, Vector<String>> ();
	
	//Service --> List of Provider
	HashMap<String, Vector<String>> serviceToProviderList = new HashMap<String, Vector<String>>();
	//Providername|Servicename --> Boolean (or null)
	HashMap<String, Boolean> provServConcToAvailableList = new HashMap<String, Boolean>();
	
	//Provider --> NotificationValue;
	HashMap<String, Integer> notifcationValueList = new HashMap<String, Integer>();
	
	//if registry Server is new, it is waiting for the actualisationSequence
	private HashMap<Integer, Vector<Object>> processingQueue = new HashMap<Integer, Vector<Object>>();

	public RegistryServer (Observer obs){
		this.addObserver(obs);
	}
	
	public void notificate(){
		if (!activated){
			this.personalTimeoutSequence--;
		}
		if (personalTimeoutSequence <= 0){
			this.executeQueue();			
		}
	}
	public Integer registerServices (BluetoothDeviceAddress provider, Vector<String> services){
		Integer retValue = null;
		if (providerList.get(provider.getAddress()) == null){
			providerList.put(provider.getAddress(), provider);
			//provider needs an notification rythm
			Vector<Object> tmp = new Vector<Object>();
			tmp.add(action_type.NOTIFY);
			tmp.add(new Integer (0));
			tmp.add(provider);
			retValue = this.createQueueEntry(tmp);
			this.notifcationValueList.put(provider.getAddress(), retValue);
		} else {
			retValue = this.notifcationValueList.get(provider.getAddress());
		}
		if (serviceList.get(provider.getAddress()) == null){
			serviceList.put(provider.getAddress(), services);
			for (String serv : services){
				if (serviceToProviderList.get(serv) == null){
					Vector<String> prov = new Vector<String>();
					prov.add(provider.getAddress());
					serviceToProviderList.put(serv, prov);
				} else {
					serviceToProviderList.get(serv).add(provider.getAddress());
				}
			}

		} else {
			for (String serv : services){
				//Combination is unknown
				if (provServConcToAvailableList.get(provider.getAddress() + "|" + serv) == null || !provServConcToAvailableList.get(provider.getAddress() + "|" + serv).booleanValue()){
					//service unknown,... just add provider
					if (serviceToProviderList.get(serv) == null){
						Vector<String> providerAddys = new Vector<String>();
						providerAddys.add(provider.getAddress());
						serviceToProviderList.put(serv, providerAddys);
					} 
					//service is known but not that it is provided by this Device?
					else {
						Vector<String> currentProvider = serviceToProviderList.get(serv);
						// oh there really should be ^^
						if (currentProvider != null){
							currentProvider.add(provider.getAddress());					
						}
						//this case normally should not happen but defens programming
						else {
							Vector<String> providerAddys = new Vector<String>();
							providerAddys.add(provider.getAddress());
							serviceToProviderList.put(serv, providerAddys);
						}						
					}						
				} 
				//Combination is known
				else {
					//nothing to do
				}		
			}				
		}
		return retValue;
	}


	/**
	 * This method is for searching for the services of an Host. In returns an Integer that represents
	 * the request number. If the server kann respond to the request, the disc. Mechanism
	 * will be informed in combination with the result and the request ID 
	 * 
	 * returns the Services for a given target-host, if the host or no 
	 * services of the host known, the method returns an empty Vector
	 * 
	 * @param target
	 * @return services or empty Vector if there are no services available
	 */
	public Integer getServicesForHost(BluetoothDeviceAddress target) {
		if (this.activated){
			return executeGetServicesForHost(target);
		} else {
			Vector<Object> content = new Vector<Object>();
			content.add(action_type.FIND_HOST_SERVICES);
			content.add(target);
			return this.createQueueEntry(content);
		}
	}
	
	/**
	 * corresponding Execute to "getServicesForHost"
	 * @param target
	 */
	private Integer executeGetServicesForHost(BluetoothDeviceAddress target){
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_HOST_SERVICES);
		obj.add(target);
		return executeImmediate(lastGlobalMsgID++, obj);
	}

	/**
	 * corresponding Execute to "getServicesForHost"
	 * @param target
	 */
	private void executeGetServicesForHost(BluetoothDeviceAddress target, Integer id){
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_HOST_SERVICES);
		if (this.serviceList.get(target.getAddress()) == null){
			obj.add(new Vector<String>());
		} else {
			obj.add(this.serviceList.get(target.getAddress()).clone());
		}
		Vector<Object> result = new Vector<Object>();
		result.add(id);
		result.add(obj);
		this.informObserver(result);
	}

	
	public Integer getProviderForService(String serviceDescription) {
		if (this.activated){
			return executeGetProviderForService(serviceDescription);
		} else {
			Vector<Object> content = new Vector<Object>();
			content.add(action_type.FIND_SPECIAL);
			content.add(serviceDescription);
			return this.createQueueEntry(content);
		}
	}

	private Integer executeGetProviderForService(String serviceDescription) {
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_SPECIAL);
		obj.add(serviceDescription);
		return executeImmediate(lastGlobalMsgID++, obj);
	}
	
	/**
	 * corresponding Execute to "getProviderForService"
	 * @param target
	 */
	private void executeGetProviderForService (String serviceDescription, Integer id){
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_SPECIAL);
		if (this.serviceToProviderList.get(serviceDescription) == null){
			obj.add(new Vector<BluetoothDeviceAddress>());
		} else {
			Vector<BluetoothDeviceAddress> providerL = new Vector<BluetoothDeviceAddress>();
			for (String prov : serviceToProviderList.get(serviceDescription)){
				providerL.add(providerList.get(prov));
			}
			obj.add(providerL);
		}
		Vector<Object> result = new Vector<Object>();
		result.add(id);
		result.add(obj);
		this.informObserver(result);
	}

	public Integer getAllAvailableServices() {
		if (this.activated){
			return executeGetAllAvailableServices();
		} else {
			Vector<Object> content = new Vector<Object>();
			content.add(action_type.FIND_ALL);
			return this.createQueueEntry(content);
		}
	}

	private Integer executeGetAllAvailableServices() {
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_ALL);
		return executeImmediate(lastGlobalMsgID++, obj);
	}

	
	/**
	 * corresponding Execute to "getAllAvailableServices"
	 * @param target
	 */
	private void executeGetAllAvailableServices(Integer id){
		Vector<Object> obj = new Vector<Object>();
		obj.add(action_type.FIND_ALL);
		if (this.serviceToProviderList.keySet().size() == 0){
			obj.add(new Vector<String>());
		} else {
			obj.add(new Vector<String>(this.serviceToProviderList.keySet()));
		}
		Vector<Object> result = new Vector<Object>();
		result.add(id);
		result.add(obj);
		this.informObserver(result);
	}

	public void updateService(BluetoothDeviceAddress bd_Addr, String oldService, String newService) {
		if (this.provServConcToAvailableList.get(bd_Addr.getAddress() + "|" + oldService) != null){
			this.provServConcToAvailableList.remove(bd_Addr.getAddress() + "|" + oldService);
			this.provServConcToAvailableList.put(bd_Addr.getAddress() + "|" + newService, new Boolean(true));
			Vector<String> services = serviceList.get(bd_Addr.getAddress());
			boolean removed = false;
			for (int i = 0; i < services.size(); i++){
				if (services.get(i).equals(oldService)){
					services.remove(i);
					removed = true;
				}
			}
			if (removed) {
				services.add(newService);
				removed = false;
			}
			Vector<String> provider = serviceToProviderList.get(oldService);
			for (int i = 0; i < provider.size(); i++){
				if (provider.get(i).equals(bd_Addr.getAddress())){
					provider.remove(i);
					removed = true;
				}
			}
			if (removed) {
				if (serviceToProviderList.get(newService) != null){
					serviceToProviderList.get(newService).add(bd_Addr.getAddress());
				} else {
					Vector<String> provL = new Vector<String>();
					provL.add(bd_Addr.getAddress());
					serviceToProviderList.put(newService, provL);					
				}
				removed = false;
				if (serviceToProviderList.get(oldService).size() == 0){
					serviceToProviderList.remove(oldService);
				}
				this.provServConcToAvailableList.put(bd_Addr.getAddress(), new Boolean(true));
			}
		}
	}

	public void removeServices(BluetoothDeviceAddress bd_Addr, Vector<String> tmp) {
		for (String serv : tmp){
			if (provServConcToAvailableList.get(bd_Addr.getAddress() + "|" + serv) != null){
				Vector<String> services = serviceList.get(bd_Addr.getAddress());
				for (int i = 0; i < services.size(); i++){
					if (services.get(i).equals(serv)){
						services.remove(i);
					}
				}
				Vector<String> provider = serviceToProviderList.get(serv);
				for (int i = 0; i < provider.size(); i++){
					if (provider.get(i).equals(bd_Addr.getAddress())){
						provider.remove(i);
					}
				}
				provServConcToAvailableList.remove(bd_Addr.getAddress() + "|" + serv);
			}
		}
		if (bd_Addr != null && serviceList.get(bd_Addr.getAddress()) != null && serviceList.get(bd_Addr.getAddress()).size() == 0){
			serviceList.remove(bd_Addr.getAddress());
			providerList.remove(bd_Addr.getAddress());
		}
	}

	
	public void pong (Integer key, BluetoothDeviceAddress bd_addr){
		this.notifcationValueList.remove(bd_addr);
		this.notifcationValueList.put(bd_addr.getAddress(), new Integer(0));
		Vector<Object> content = this.processingQueue.get(key);
		action_type actiontype = (action_type) content.get(0);
		if (actiontype == action_type.PINGED){
			actiontype = action_type.NOTIFY;
		}
	}
	//////////////////////////////////////////
	//VERY PRIVAT ^^
///////////////////////////////////////////	
	/**
	 * this method ist for sort the request into the Queue
	 * 
	 * content-design
	 * 0. Action Description
	 * 1. Action-Specific Content
	 * 2. Action-Specific Content to
	 * @param content
	 * @return Unique Request ID
	 */
	private Integer createQueueEntry (Vector<Object> content){
		Integer returnValue = new Integer(lastGlobalMsgID++);
		this.processingQueue.put(returnValue, content);
		return returnValue;
	}
	
	/**
	 * this method should execute every content in the queue
	 */
	private void executeQueue(){
		for (Integer key : this.processingQueue.keySet()){
			Vector<Object> content = this.processingQueue.get(key);
			this.executeImmediate(key, content);
		}
	}
	
	/**
	 * for execute an inform immediate
	 * 
	 * this method simply returns the Value that the method becomes as uniqueRequeID
	 * @param uniqueRequID
	 * @param obj
	 * @return
	 */
	private Integer executeImmediate(Integer uniqueRequID, Vector<Object> content){
		//first part	
		action_type actiontype = (action_type) content.get(0);
		switch (actiontype){
		case FIND_HOST_SERVICES:
			executeGetServicesForHost((BluetoothDeviceAddress) content.get(1), uniqueRequID);
			break;
		case FIND_SPECIAL:
			executeGetProviderForService((String) content.get(1), uniqueRequID);
			break;
		case FIND_ALL:
			executeGetAllAvailableServices(uniqueRequID);
			break;
		case PINGED:
			Integer count_p = (Integer) content.get(1);
			if (count_p.intValue() == NOTIFY_SEQUENCE){
				this.processingQueue.remove(uniqueRequID);
				BluetoothDeviceAddress prov = ((BluetoothDeviceAddress)content.get(2));
				this.providerList.remove(prov.getAddress());
				Vector<String> serv = serviceList.get(prov.getAddress());
				for (int i = 0 ; i < serv.size(); ){
					this.provServConcToAvailableList.remove(prov.getAddress() + "|" + serv.get(i));
					Vector<String> provList = this.serviceToProviderList.get(serv.get(i));
					for (int k = 0; k < provList.size(); k++){
						if (provList.get(k).equals(prov.getAddress())){
							provList.remove(k);
						}
					}
					if (provList.size() == 0){
						this.serviceToProviderList.remove(serv.get(i));
					}
				}
			} else {
				count_p = new Integer (count_p.intValue()+ 1);	
			}
			break;
		case NOTIFY:
			Integer count_n = (Integer) content.get(1);
			if (count_n.intValue() == NOTIFY_SEQUENCE){
				count_n = new Integer(0);
				actiontype = action_type.PINGED;
				Vector<Object> result = new Vector<Object>();
				result.add(uniqueRequID);
				this.informObserver(result);
			} else {
				count_n++;	
			}
			break;
		}
		return uniqueRequID;
	}
	
	public Object exchangeTable (){
		Vector<Object> content = new Vector<Object>();
		content.add(providerList.clone());
		content.add(serviceList.clone());
		return content;
	}

	@SuppressWarnings("unchecked")
	public void exchangeTable (Object obj){
		Vector<Object> content = (Vector<Object>) obj;
		HashMap<String, BluetoothDeviceAddress> tmp_providerList = (HashMap<String, BluetoothDeviceAddress>) content.get(0);
		HashMap<String, Vector<String>> tmp_serviceList = (HashMap<String, Vector<String>>) content.get(1);
		for (BluetoothDeviceAddress addy : tmp_providerList.values()){
			this.registerServices(addy, tmp_serviceList.get(addy.getAddress()));
		}
	}

	/**
	 * Methods for inform Observer that something happened
	 * @param obj 
	 * 
	 * content design of obj = Vector<Object>
	 * 0. Integer --> Unique Request ID
	 * 1. Vector<Object>
	 * 	0. Action
	 * 	1. Action Specific Content
	 * 	2. Action Specific Content
	 * 
	 */
	private void informObserver(Object obj){
		this.setChanged();
		this.notifyObservers(obj);
	}
	
}
